"""

.. module:: database_test
.. moduleauthor:: @author: 

*Description*:

This module is the unit test for the DataStore module.

*Constants*:

.. seealso:: class :class:`datastore.database.DataStore`
"""

# Imports
#
import datetime
import os
import unittest

from datastore.database import *
from datastore.uri import Uri

# Constants
#
TEST_DATABASE = ':memory:'

# Classes
#
class DataStoreTest(unittest.TestCase):
    """
    """
    
    def setUp(self):
        pass

    def tearDown(self):
        # removes the database file after a test is run
        if os.access(TEST_DATABASE, os.F_OK):
            os.remove(TEST_DATABASE)

    def test_put_case_01(self):
        """
        [database] Test the resource is saved successfully 
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        ds.put(resource1)
        if not resource1.get_id():
            self.fail('Save failed')

    def test_put_case_02(self):
        """
        [database] Test saving two resources successfully 
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource2 = Resource(type='term:base/datastore/resource/test')
        ds.put([resource1, resource2])
        if not resource1.get_id() and not resource2.get_id():
            self.fail('Save failed')
        
    def test_put_case_03(self):
        """
        [database] Test saving with invalid type 
        """
        
        ds = DataStore(TEST_DATABASE)
        dt = datetime.datetime.now()
        try:
            ds.put(dt)
        except TypeError:
            assert True
        else:
            self.fail('Should not be able to put a datetime object')
        
    def test_put_case_04(self):
        """
        [database] Test saving with two instances of the DataStore 
        """
        
        ds_1 = DataStore(TEST_DATABASE)
        ds_2 = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource2 = Resource(type='term:base/datastore/resource/test')
        ds_1.put(resource1)
        ds_2.put(resource2)
        
        # check ids have been assigned
        if not resource1.get_id() and not resource2.get_id():
            self.fail('Save failed')        
        
    def test_put_case_05(self):
        """
        [database] Test updating a resource 
        """
        
        key = Uri('dummykey')
        value = Uri('dummyvalue')
        attributevalue = 'dummyattributevalue'
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        
        # save resource
        ds.put(resource1)
        
        # change resource metadata and attributes
        resource1.set_metadata(key, value)
        resource1.attribute = attributevalue
        
        # update resource
        ds.put(resource1)
        resource2 = ds.get(resource1.get_id())[0]
        
        # checks
        if resource1.get_id() == "1":
            try:
                if resource2.attribute != attributevalue:
                    self.fail("Object did not update")
            except KeyError:
                self.fail("Object did not update")
            try:
                if resource2.get_metadata(key) != value:
                    self.fail("Metadata did not update")
            except KeyError:
                self.fail("Metadata did not update")                
        else:
            self.fail("New instance shouldn't be created")
    
    def test_put_case_06(self):
        """
        [database] Test updating two resources successfully 
        """
        
        key1 = Uri('key1')
        value1 = Uri('value1')
        key2 = Uri('key2')
        value2 = Uri('value2')
        value3 = 'value3'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1, multiple=True)
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource2.attribute1 = value2
        
        # save resources
        ds.put([resource1, resource2])
        
        # change resource metadata and attributes
        resource1.set_metadata(key1, value2)
        resource1.set_metadata(key2, value3)
        resource1.attribute1 = value1
        resource2.set_metadata(key2, value1)
        resource2.attribute2 = value2
        
        # update resource
        ds.put([resource1, resource2])
        
        # retrieve updated resources
        resources = ds.get([resource1.get_id(), resource2.get_id()])
        if resources[0].get_id() == "1":
            res1 = resources[0]
            res2 = resources[1]
        else:
            res2 = resources[0]
            res1 = resources[1]
        
        # check we have assigned the resources correctly before we test 
        # updates based on this assignment
        self.assertEqual(res1.get_id(), "1")
        self.assertEqual(res2.get_id(), "2")
        
        # checks both resources have been updated
        self.assertEqual(res1.get_metadata(key1), [value1, value2])
        self.assertEqual(res1.get_metadata(key2), value3)
        self.assertEqual(res1.attribute1, value1)
        self.assertEqual(res2.get_metadata(key2), value1)
        self.assertEqual(res2.attribute1, value2)
        self.assertEqual(res2.attribute2, value2)

    def test_put_case_07(self):
        """
        [database] Test updating two instances with different 
        DataStore instances 2
        """
        
        # If an in-memory datastore is used, then each is separate, so 
        # ignore this test.
        if TEST_DATABASE == ':memory:':
            print("\nThis test is skipped for an in-memory datastore.")
            return
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        value3 = 'value3'        
        ds_1 = DataStore(TEST_DATABASE)
        ds_2 = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1, multiple=True)
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource2.attribute1 = value1
        
        self.assertEqual(resource1.get_metadata(key1), [value1])
        self.assertEqual(resource2.attribute1, value1)
        
        # save resources in different datastore instances
        ds_1.put([resource1])
        ds_2.put([resource2])

        # change resource metadata and attributes
        resource2.attribute1 = value2
        resource1.set_metadata(key1, value2)
        resource1.set_metadata(key2, value3, multiple=True)
        resource1.attribute1 = value1
        resource2.set_metadata(key2, value1)
        resource2.attribute2 = value2

        # now update resource in the other datastore instance        
        ds_1.put([resource2])
        ds_2.put([resource1])
                
        # retrieve both resources from the first datastore
        resources = ds_1.get([resource1.get_id(), resource2.get_id()])
        
        # make sure res1 == id1 and res2 == id2
        if resources[0].get_id() == "1":
            res1 = resources[0]
            self.assertEqual(resources[1].get_id(), "2")
            res2 = resources[1]
        else:
            self.assertEqual(resources[0].get_id(), "2")
            self.assertEqual(resources[1].get_id(), "1")
            res1 = resources[1]
            res2 = resources[0]
        
        # checks both resources have been updated
        self.assertEqual(res1.get_metadata(key1), [value1, value2])
        self.assertEqual(res1.attribute1, value1)
        # For XML serialisation (the old method) this resulted in value3, 
        # and for the repr format, its [value3], the more correctly value. This 
        # is a limitation in the XML datastructure used.
        self.assertEqual(res1.get_metadata(key2), [value3])
        self.assertEqual(res2.attribute1, value2)
        self.assertEqual(res2.get_metadata(key2), value1)
        self.assertEqual(res2.attribute2, value2)        

    def test_get_case_01(self):
        """
        [database] Test getting the resource with valid id 
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        ds.put([resource1])
        resources = ds.get("1")
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return is incorrect')
    
    def test_get_case_02(self):
        """
        [database] Test getting the resource with invalid id 
        """
        
        ds = DataStore(TEST_DATABASE)
        try:
            ds.get("aaa")
        except NotFoundException:
            assert True
        else:
            self.fail('get should have failed as "aaa" id should not exist')  
        
    def test_get_case_03(self):
        """
        [database] Test getting the resource with valid ids 
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource2 = Resource(type='term:base/datastore/resource/test')
        ds.put([resource1, resource2])
        resources = ds.get(["1", "2"])
        if len(resources) == 2  and resource1 in resources \
                                and resource2 in resources:
            assert True
        else:
            self.fail('Resources return is incorrect')
        
    def test_get_case_04(self):
        """
        [database] Test getting the resource with one invalid id in ids 
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource2 = Resource(type='term:base/datastore/resource/test')
        ds.put([resource1, resource2])
        try:
            ds.get(["1", "-1"])
        except NotFoundException:
            assert True
        else:
            self.fail('should fail will non valid id (-1)')        
    
    def test_delete_case_01(self):
        """
        [database] Test deleting persisted resource 
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        ds.put(resource1)
        resource2 = ds.get(resource1.get_id())
        if resource1 == resource2[0]:
            ds.delete(resource1)
            try:
                # try to retrieve it to make sure its been deleted
                ds.get(resource1.get_id())
            except NotFoundException:
                assert True
            else:
                self.fail('Delete Failed')
        else:
            self.fail("Put or Get failed")
        
    def test_delete_case_02(self):
        """
        [database] Test deleting non-persisted resource 
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        # should we throw an error if we try to delete a resource that is 
        # not persisted?
        ds.delete(resource1)
        
    
    def test_delete_case_03(self):
        """
        [database] Test deleting invalid type 
        """
        
        ds = DataStore(TEST_DATABASE)
        try:
            ds.delete([datetime.datetime.now()])
        except TypeError:
            assert True
        else:
            self.fail('Should fail with wrong type')
        
    def test_find_case_01(self):
        """
        [database] Test find resource with key and value 
        """
        
        key = Uri('dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key, value)
        ds.put([resource1])
        resources = ds.find([(key,value)])
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return is incorrect')
    
    def test_find_case_02(self):
        """
        [database] Test find resource with key and no value 
        """
        
        key = Uri('dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key, value)
        ds.put([resource1])
        resources = ds.find([(key, None)])
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return is incorrect')
        
    def test_find_case_03(self):
        """
        [database] Test find resource with no matching key and value 
        combination 
        """
        
        key = Uri('dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        try:
            ds.find([(key,value)])
        except NotFoundException:
            assert True
        else:
            self.fail('Find should fail as no Resources match the key / value')
        
    def test_find_case_04(self):
        """
        [database] Test find resource with no matching key 
        """
        
        key = Uri('dummykey')
        ds = DataStore(TEST_DATABASE)
        try:
            ds.find([(key, None)])
        except NotFoundException:
            assert True
        else:
            self.fail('Find should fail as no Resources match the key')

    def test_find_case_05(self):
        """
        [database] Test find resource with multiple key1 and value1 
        """
        
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1)
        resource1.set_metadata(key2, value2)        
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource2.set_metadata(key2, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(key1,value1), (key2,value2)])
        if len(resources) == 1 and resource1 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')
    
    def test_find_case_06(self):
        """
        [database] Tests find resource with multiple key and key and value 
        """
        
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1)
        resource1.set_metadata(key2, value2)        
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource2.set_metadata(key2, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(key1,value1), (key2,None)])
        if len(resources) == 1 and resource1 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')

    def test_find_case_07(self):
        """
        [database] Tests find resource with one key that returns multiple resources 
        """
        
        key1 = Uri('key1')
        value1 = 'value1'
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1)
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource2.set_metadata(key1, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(key1,None)])
        if len(resources) == 2 and resource1 in resources \
                               and resource2 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')

    def test_find_case_08(self):
        """
        [database] Tests find resource with one key that returns multiple resources 
        """
        
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1)
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource2.set_metadata(key1, value2)
        ds.put([resource1, resource2])
        try:
            ds.find([(key1, None), (key2, None)])
        except NotFoundException:
            assert True
        else:
            self.fail('Should not find key2')            

    def test_find_case_09(self):
        """
        [database] Tests find resource with multiple keys that return one resource 
        """
        
        key1 = Uri('key1')
        value1 = Uri('value1')
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1)
        resource1.set_metadata(key2, value2)
        ds.put([resource1])
        resources = ds.find([(key1,None), (key2,None)])
        if len(resources) == 1 and resource1 in resources:
            assert True
        else:
            self.fail('Resource return is incorrect')

                    
    def test_find_case_10(self):
        """
        [database] Test find resource with invalid key type 
        """
        
        ds = DataStore(TEST_DATABASE)
        try:
            ds.find(datetime.datetime.now())
        except TypeError:
            assert True
        else:
            self.fail('Datetime type should not be allowed to be used ' + \
                      'as a key_value list')
            
    def test_find_case_11(self):
        """
        [database] Test find resource with key and value and that 
        resource has the ns information to expand uris 
        """
        
        key = Uri('internal:dummykey')
        value = 'dummyvalue'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key, value)
        ds.put([resource1])
        resources = ds.find([(Uri('http://node.internal/dummykey'),value)])
        if len(resources) == 1 and resources[0] == resource1:
            # Test metadata on the resource can expand its own uri to 
            # match the one passed in
            self.assertEqual(resources[0].get_metadata(
                                Uri('http://node.internal/dummykey')), value)
        else:
            self.fail('Resource return is incorrect')            

    def test_find_case_12(self):
        """
        [database] Tests find resource with None for key and value - wildcard. 
        """
        
        key1 = Uri('key1')
        value1 = 'value1'
        key2 = Uri('key2')
        value2 = 'value2'
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_metadata(key1, value1)
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource2.set_metadata(key1, value2)
        ds.put([resource1, resource2])
        resources = ds.find([(None, None)])
        self.assertTrue(len(resources) == 2)
        
    def test_find_type_case_01(self):
        """
        [database] Test find_type resource with key and value 
        """
        
        value = Uri('dummyvalue')
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource1.set_type(Uri(value))
        ds.put([resource1])
        resources = ds.find_type(value)
        if len(resources) == 1 and resources[0] == resource1:
            assert True
        else:
            self.fail('Resource return for find_type is incorrect')
        
    def test_delete_put_case_01(self):
        """
        [database] Test delete and put a resource in the same db transaction
        """
        
        ds = DataStore(TEST_DATABASE)
        resource1 = Resource(type='term:base/datastore/resource/test')
        resource2 = Resource(type='term:base/datastore/resource/test')
        resource3 = Resource(type='term:base/datastore/resource/test')
        ds.put([resource1, resource2])
        self.assertTrue(len(ds.get([resource1.get_id(), 
                                     resource2.get_id()])) == 2)
        ds.delete_put([resource1], [resource3])
        self.assertTrue(ds.get([resource3.get_id()]))
        self.assertRaises(NotFoundException, ds.get, [resource1.get_id()])
    
    def test_log_case_01(self):
        """
        [database] Test saving log message with one instance of the DataStore 
        """
        
        ds_1 = DataStore(TEST_DATABASE)
        message = "This is a log message."
        msg_id = ds_1.log(message)
        msg_tuple_list = ds_1.get_log([msg_id])
        found = False
        for msg_tuple in msg_tuple_list:
            if (msg_tuple[0] == message):
                found = True
        
        self.assertEqual(found, True, "Log message not found in DataStore.")
    
    def test_log_case_02(self):
        """
        [database] Test retrieving many log messages with one call. 
        """
        
        ds_1 = DataStore(TEST_DATABASE)
        message1 = "This is a log message."
        msg_id1 = ds_1.log(message1)
        message2 = "This is another log message."
        msg_id2 = ds_1.log(message2)
        msg_tuple_list = ds_1.get_log([msg_id1, msg_id2])
        found = False
        for msg_tuple in msg_tuple_list:
            if (msg_tuple[0] == message1) or (msg_tuple[0] == message2):
                found = True
            else:
                found = False
                break
        
        self.assertEqual(found, True, "Log messages not found in DataStore.")
    
    def test_log_case_03(self):
        """
        [database] Test retrieving all log messages with one call. 
        """
        
        ds_1 = DataStore(TEST_DATABASE)
        message1 = "This is a log message."
        msg_id1 = ds_1.log(message1)
        message2 = "This is another log message."
        msg_id2 = ds_1.log(message2)
        msg_tuple_list = ds_1.get_log()
        found = False
        for msg_tuple in msg_tuple_list:
            if (msg_tuple[0] == message1) or (msg_tuple[0] == message2):
                found = True
            else:
                found = False
                break
        
        self.assertEqual(found, True, "Log messages not found in DataStore.")
    
    def test_get_n_logs(self):
        ds_1 = DataStore(TEST_DATABASE)
        message1 = "This is a log message."
        ds_1.log(message1)
        message2 = "This is another log message."
        ds_1.log(message2)
        message3 = "This is the last log message."
        ds_1.log(message3)
        msg_tuple_list = ds_1.get_n_logs(n=2, desc=True)
        self.assertEqual(2, len(msg_tuple_list))
        self.assertEqual(message3, msg_tuple_list[0][0])
        self.assertEqual(message2, msg_tuple_list[1][0])

        msg_tuple_list = ds_1.get_n_logs(n=3, desc=True)
        self.assertEqual(3, len(msg_tuple_list))
        self.assertEqual(message3, msg_tuple_list[0][0])
        self.assertEqual(message2, msg_tuple_list[1][0])
        self.assertEqual(message1, msg_tuple_list[2][0])
        
        msg_tuple_list = ds_1.get_n_logs(n=2, desc=False)
        self.assertEqual(2, len(msg_tuple_list))
        self.assertEqual(message1, msg_tuple_list[0][0])
        self.assertEqual(message2, msg_tuple_list[1][0])   
        
        msg_tuple_list = ds_1.get_n_logs(n=4, desc=False)
        self.assertEqual(3, len(msg_tuple_list))
        self.assertEqual(message1, msg_tuple_list[0][0])
        self.assertEqual(message2, msg_tuple_list[1][0])
        self.assertEqual(message3, msg_tuple_list[2][0])                
          
        
if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.test_']
    unittest.main()